/**********************************************************************************************************************
 * @file    flash.h
 * @author  Queclink Hubert.Lee
 * @date    2016-10-31
 * @brief   片外Flash模块头文件
 * @ingroup aadl
 *
 * Copyright (C) 2016 Queclink Wireless Solutions (ShenZhen) Co., Ltd. All Rights Reserved.
 *
 * @attention
 * 1. aad_flash_initialize(): 必须是第一个被调用的函数;
 * 2. aad_flash_format(): Flash格式化函数, 必须至少被调用一次;
 * 3. aad_flash_process(): 必须在主循环中调用本函数;
 * 4. 第1、2扇区固定保留给模块内部使用;
 * 5. 对环形存储区, 固定按存储时间先后顺序读取, 即FIFO;
 * 6. 对文件存储区, 模块初始化后文件写指针和读指针默认都指向文件起始位置;
 * 7. 模块内部对所有数据读写时都进行了CRC校验, 保障数据合法有效;
 * 8. 本模块是基于无操作系统环境开发的, 当应用于操作系统环境时, 务必保证仅在同一个线程(任务)中使用本模块;
 * 9. 本模块仅适用于单Flash的硬件环境.
 * 10.本模块目前只支持 AADL_WAIT 选项, 不支持 AADL_NO_WAIT 选项.
 *
 * @usage
 * 1. int aad_flash_initialize(struct flash_adapter *)
 *   @brief  初始化Flash模块;
 *   @param  struct flash_adapter *: [I], Flash适配接口函数;
 *   @retval 0:成功, others:错误码
 * 2. void aad_flash_process(void)
 *   @brief  Flash主循环处理函数.
 * 3. int aad_flash_attribute_get(struct flash_attribute *)
 *   @brief  获取Flash属性;
 *   @param  struct flash_attribute *: [O], 获取到的属性;
 *   @retval 0:成功, others:错误码
 * 4. aadl_cmd_state aad_flash_cmd_state_get(int id)
 *   @brief  获取命令执行状态;
 *   @param  id: [I], 命令ID;
 *   @retval 当前命令执行状态
 * 5. int aad_flash_erase_chip(aadl_option option_set, unsigned int timeout)
 *   @brief  Flash整片擦除函数;
 *   @param  option_set: [I], 选项组合, AADL_NO_WAIT/AADL_WAIT;
 *           timeout: [I], 超时时间, 单位s, 仅当 AADL_WAIT 时有效, AADL_NO_TIMEOUT:不超时;
 *   @retval 负数:错误码; 0:当 AADL_WAIT 时表示命令执行成功, 当 AADL_NO_WAIT 时为命令ID; 正数:命令ID
 * 6. int aad_flash_format(const struct flash_zone *, size_t count, aadl_option option_set, unsigned int timeout)
 *   @brief  Flash格式化函数: 检查当前Flash所有分区是否合法有效, 无效则重新格式化该分区;
 *   @param  struct flash_zone *: [I], 合法有效的分区表;
 *           count: [I], 分区个数;
 *           option_set: [I], 选项组合, AADL_NO_WAIT/AADL_WAIT/AADL_FORCE;
 *           timeout: [I], 超时时间, 单位s, 仅当 AADL_WAIT 时有效, AADL_NO_TIMEOUT:不超时;
 *   @note   AADL_FORCE:当不使用强制选项时,格式化过程中遇到错误会返回错误码;当使用强制选项时,
 *           格式化过程中遇到某些问题(如分区大小的合理调整)需要重新进行格式化时,将自动进行重新格式化,
 *           并尽最大努力保留用户数据(至少保留第1扇区以及第1个关键数据区的内容).
 *   @retval 负数:错误码; 0:当 AADL_WAIT 时表示命令执行成功, 当 AADL_NO_WAIT 时为命令ID; 正数:命令ID
 * 7. int aad_flash_format_fully(const struct flash_zone *, size_t count, aadl_option option_set, unsigned int timeout)
 *   @brief  Flash完全格式化函数: 擦除整片Flash, 重新格式化整片Flash;
 *   @param  see 'aad_flash_format'
 *   @note   特别地, 完全格式化支持 AADL_FORCE 选项, 当不使用该选项时, 不擦除第一扇区数据, 当使用该选项时, 擦除整片数据再进行格式化.
 *   @retval 负数:错误码; 0:当 AADL_WAIT 时表示命令执行成功, 当 AADL_NO_WAIT 时为命令ID; 正数:命令ID
 * 8. int aad_flash_debug_set(char *loglevel)
 *   @brief  设置Flash模块LOG等级;
 *   @param  loglevel: [I], LOG等级, LOG_OFF:关闭打印功能;
 *   @retval 0:成功, others:错误码
 **********************************************************************************************************************
 * 9. int aad_flash_direct_write(int zone_id, uint32_t offset_addr, const uint8_t *buf, size_t size, aadl_option option_set, unsigned int timeout)
 *   @brief  用户直接存储区写函数;
 *   @param  offset_addr: [I], 偏移地址;
 *           buf: [I], 待写入数据;
 *           size: [I], 待写入数据长度;
 *           option_set: [I], 选项组合, AADL_NO_WAIT/AADL_WAIT,AADL_KEEP_OUTSIDE;
 *           timeout: [I], 超时时间, 单位s, 仅当 AADL_WAIT 时有效, AADL_NO_TIMEOUT:不超时;
 *   @note   AADL_KEEP_OUTSIDE: 使用该选项时将保留对应扇区的其它数据, 否则不保留;
 *   @retval 负数:错误码; 0:当 AADL_WAIT 时表示命令执行成功, 当 AADL_NO_WAIT 时为命令ID; 正数:命令ID
 * 10. ssize_t aad_flash_direct_read(int zone_id, uint32_t offset_addr, uint8_t *buf, size_t size)
 *   @brief  用户直接存储区读函数
 *   @param  offset_addr: [I], 偏移地址;
 *           buf: [O], 读取到的数据;
 *           size: [I], 待读取字节数;
 *   @retval 非负数:实际读取到的字节数, others:错误码
 * 11. int aad_flash_zone_write(int zone_id, int data_id, const uint8_t *buf, aadl_option option_set, unsigned int timeout)
 *   @brief  离散存储区、关键数据区写函数;
 *   @param  zone_id: [I], 分区ID;
 *           data_id: [I], 数据ID;
 *           buf: [I], 待写入数据;
 *           option_set: [I], 选项组合, AADL_NO_WAIT/AADL_WAIT;
 *           timeout: [I], 超时时间, 单位s, 仅当 AADL_WAIT 时有效, AADL_NO_TIMEOUT:不超时;
 *   @retval 负数:错误码; 0:当 AADL_WAIT 时表示命令执行成功, 当 AADL_NO_WAIT 时为命令ID; 正数:命令ID
 * 12. ssize_t aad_flash_zone_read(int zone_id, int data_id, uint8_t *buf)
 *   @brief  离散存储区、关键数据区读函数
 *   @param  zone_id: [I], 分区ID;
 *           data_id: [I], 数据ID;
 *           buf: [O], 读取到的数据;
 *   @retval 非负数:实际读取到的字节数, others:错误码
 **********************************************************************************************************************
 * 13. int aad_flash_file_wseek(int zone_id, uint32_t offset)
 *   @brief  设置文件写指针(设置且写入新数据后, 指针后的原数据将全部丢失)
 *   @note   环形文件分区不支持设置文件写指针
 *   @param  zone_id: [I], 分区ID;
 *           offset: [I], 偏移地址;
 *   @retval 0:成功, others:错误码
 * 14. int aad_flash_file_rseek(int zone_id, uint32_t offset)
 *   @brief  设置文件读指针(环形文件分区不支持设置文件读指针)
 *   @param  zone_id: [I], 分区ID;
 *           offset: [I], 偏移地址;
 *   @retval 0:成功, others:错误码
 * 15. int aad_flash_file_write(int zone_id, const uint8_t *buf, size_t size, aadl_option option_set, unsigned int timeout)
 *   @brief  文件存储区写函数;
 *   @param  zone_id: [I], 分区ID;
 *           buf: [I], 待写入数据;
 *           size: [I], 待写入字节数;
 *           option_set: [I], 选项组合, AADL_NO_WAIT/AADL_WAIT;
 *           timeout: [I], 超时时间, 单位s, 仅当 AADL_WAIT 时有效, AADL_NO_TIMEOUT:不超时;
 *   @retval 负数:错误码; 0:当 AADL_WAIT 时表示命令执行成功, 当 AADL_NO_WAIT 时为命令ID; 正数:命令ID
 * 16. ssize_t aad_flash_file_read(int zone_id, uint8_t *buf, size_t size)
 *   @brief  文件存储区读函数
 *   @param  zone_id: [I], 分区ID;
 *           buf: [O], 读取到的数据;
 *           size: [I], 待读取字节数;
 *   @retval 非负数:实际读取到的字节数, others:错误码
 * 17. int aad_flash_file_erase(int zone_id, aadl_option option_set, unsigned int timeout)
 *   @brief  文件擦除函数;
 *   @note   文件擦除之后, 再执行写请求(aad_flash_file_write)时不再执行擦除操作;
 *   @param  zone_id: [I], 分区ID;
 *           option_set: [I], 选项组合, AADL_NO_WAIT/AADL_WAIT;
 *           timeout: [I], 超时时间, 单位s, 仅当 AADL_WAIT 时有效, AADL_NO_TIMEOUT:不超时;
 *   @retval 负数:错误码; 0:当 AADL_WAIT 时表示命令执行成功, 当 AADL_NO_WAIT 时为命令ID; 正数:命令ID
 * 18. int aad_flash_file_size_get(int zone_id, size_t *total_size, size_t *used_size)
 *   @brief  文件存储区用量查询
 *   @note   FZONE_FILE 类型分区不存储"使用量"信息, 重启后无法查询.
 *   @param  zone_id: [I], 分区ID;
 *           total_size: [O], 分区容量;
 *           used_size: [O], 已写入的字节数;
 *   @retval 0:成功, others:错误码
 **********************************************************************************************************************
 * 19. int aad_flash_circular_write(int zone_id, const uint8_t *buf, size_t size, aadl_option option_set, unsigned int timeout)
 *   @brief  环形存储区写函数;
 *   @param  zone_id: [I], 分区ID;
 *           buf: [I], 待写入数据;
 *           size: [I], 待写入数据长度;
 *           option_set: [I], 选项组合, AADL_NO_WAIT/AADL_WAIT;
 *           timeout: [I], 超时时间, 单位s, 仅当 AADL_WAIT 时有效, AADL_NO_TIMEOUT:不超时;
 *   @retval 负数:错误码; 0:当 AADL_WAIT 时表示命令执行成功, 当 AADL_NO_WAIT 时为命令ID; 正数:命令ID
 *           -ENOSPC:空间不足; -EIO:读写错误;
 * 20. ssize_t aad_flash_circular_read(int zone_id, uint8_t *buf, size_t max_size)
 *   @brief  环形存储区读函数(读取成功后删除该数据项)
 *   @param  zone_id: [I], 分区ID;
 *           buf: [O], 读取到的数据, NULL:读取并丢弃数据;
 *           max_size: [I], 最大读取长度(模块优先按照分区表长度读取, 若分区表长度大于本长度(异常), 按本长度读取);
 *   @retval 非负数:实际读取到的字节数, others:错误码
 * 21. ssize_t aad_flash_circular_peek(int zone_id, uint8_t *buf, size_t max_size)
 *   @brief  环形存储区预读函数(此操作不会删除该数据项)
 *   @param  zone_id: [I], 分区ID, 可以为 FLASH_ZONE_NULL;
 *           buf: [O], 读取到的数据;
 *           max_size: [I], 最大读取长度(模块优先按照分区表长度读取, 若分区表长度大于本长度(异常), 按本长度读取);
 *   @note   FLASH_ZONE_NULL: 特别地,当分区ID为空ID时,表示预读"下一个"记录,作用与strtok(NULL,xxx)类似,当peek(id,xxx,xxx)的id是非空ID时,
 *           固定预读第一个未被读取的记录,当id为空ID(FLASH_ZONE_NULL)时,预读之前已经预读的记录的下一个记录.
 *   @retval 非负数:实际读取到的字节数, others:错误码
 * 22. int aad_flash_circular_move(int dest_zone_id, int src_zone_id, unsigned int count, aadl_option option_set, unsigned int timeout)
 *   @brief  环形存储区转移函数, 要求两者都是环形存储区
 *   @param  dest_zone_id: [I], 目的分区ID;
 *           src_zone_id: [I], 源分区ID;
 *           count: [I], 待转移的单元数据数量, 0xFFFFFFFF:转移整个分区数据;
 *           option_set: [I], 选项组合, AADL_NO_WAIT/AADL_WAIT;
 *           timeout: [I], 超时时间, 单位s, 仅当 AADL_WAIT 时有效, AADL_NO_TIMEOUT:不超时;
 *   @retval 负数:错误码; 0:当 AADL_WAIT 时表示命令执行成功, 当 AADL_NO_WAIT 时为命令ID; 正数:命令ID
 * 23. ssize_t aad_flash_circular_count_get(int zone_id)
 *   @brief  获取环形存储区当前字段(数据)数目;
 *   @param  zone_id: [I], 分区ID;
 *   @retval 非负数:当前字段(数据)数目, others:错误码
 * 24. int aad_flash_circular_size_get(int zone_id, size_t *total_size, size_t *used_size)
 *   @brief  环形存储区用量查询
 *   @param  zone_id: [I], 分区ID;
 *           total_size: [O], 分区总容量;
 *           used_size: [O], 已使用的字节数;
 *   @retval 0:成功, others:错误码
 **********************************************************************************************************************
 * 25. int aad_flash_frequency_write(int zone_id, const uint8_t *buf, aadl_option option_set, unsigned int timeout)
 *  @brief  频繁存储区写函数;
 *  @param  zone_id: [I], 分区ID;
 *          buf: [I], 待写入数据;
 *          option_set: [I], 选项组合, AADL_NO_WAIT/AADL_WAIT;
 *          timeout: [I], 超时时间, 单位s, 仅当 AADL_WAIT 时有效, AADL_NO_TIMEOUT:不超时;
 *  @retval 负数:错误码; 0:当 AADL_WAIT 时表示命令执行成功, 当 AADL_NO_WAIT 时为命令ID; 正数:命令ID
 * 26. ssize_t aad_flash_frequency_read(int zone_id, uint8_t *buf)
 *  @brief  频繁存储区读函数
 *  @param  zone_id: [I], 分区ID;
 *          buf: [O], 读取到的数据;
 *  @retval 非负数:实际读取到的字节数, others:错误码
 **********************************************************************************************************************
 * 27. int aad_flash_zone_clear(int zone_id, aadl_option option_set, unsigned int timeout)
 *   @brief  清空Flash分区的用户数据;
 *   @note   仅作清空标记, 未删除实际数据;
 *   @param  zone_id: [I], 分区ID;
 *           option_set: [I], 选项组合, AADL_NO_WAIT/AADL_WAIT;
 *           timeout: [I], 超时时间, 单位s, 仅当 AADL_WAIT 时有效, AADL_NO_TIMEOUT:不超时;
 *   @retval 负数:错误码; 0:当 AADL_WAIT 时表示命令执行成功, 当 AADL_NO_WAIT 时为命令ID; 正数:命令ID
 * 28. int aad_flash_callback_register(int id, void (*callback)(bool success, void *ptr), void *ptr)
 *   @brief  为命令注册回调函数(仅当命令执行结束时调用).
 *   @param  callback: [I], 回调函数, success:命令执行结果,true-成功,false-失败, ptr:用户指针;
 *           ptr: [I], 回调函数使用的用户指针;
 *   @note   当成功注册回调函数后, 切勿使用"aad_flash_cmd_state_get"查询命令状态, 否则回调可能失效.
 *   @retval 0:成功, others:错误码
 * 29. int aad_flash_enter_lowpower(void)
 *   @brief  设置Flash进入低功耗模式
 *   @note   若用户进入低功耗模式后,未主动退出便进行读写操作,则Flash库将先执行退出操作,再执行读写操作.
 *   @retval 0:成功, others:错误码
 * 30. int aad_flash_leave_lowpower(void)
 *   @brief  设置Flash退出低功耗模式
 *   @note   若用户进入低功耗模式后,未主动退出便进行读写操作,则Flash库将先执行退出操作,再执行读写操作.
 *   @retval 0:成功, others:错误码
 *
 *********************************************************************************************************************/


#ifndef __AADL_FLASH_H
#define __AADL_FLASH_H

/* includes ---------------------------------------------------------------------------------------------------------*/
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <aadl/aadl.h>

#ifdef __cplusplus
extern "C" {
#endif


/* FLASH_DESTROYED: Flash损坏事件,相同扇区从MCU复位起只汇报一次 */
typedef enum {
    FLASH_DESTROYED,            /* Flash损坏, unsigned int:损坏的扇区号(从1开始) */
}flash_event_id;

struct flash_adapter {
    int     (*enable)(void);        /* access enable */
    int     (*disable)(void);       /* access disable */
    ssize_t (*write)(const unsigned char *, size_t);    /* write to Flash */
    ssize_t (*read)(unsigned char *, size_t);           /* read from Flash */
    void    (*events_handler)(flash_event_id, void *);  /* Flash事件回调函数, NULL:不需要该功能 */
};

struct flash_attribute {
    char    model[16];          /* 型号, 字符串 */
    size_t  total_size;         /* 总大小, 单位byte */
    size_t  sector_size;        /* 扇区大小, 单位byte */
    size_t  block_size;         /* 块大小, 单位byte */
};

/* flash zone type */
typedef enum {
    FZONE_DIRECT,       /* 用户直接读写区, 由用户指定读写地址 */
    FZONE_DISCRETE,     /* 离散存储区, 存储地址由模块内部分配, 用户通过数据ID访问 */
    FZONE_FATAL,        /* 关键数据区, 存储至关重要的参数, 具有自动备份功能, 用户通过数据ID访问 */
    FZONE_FILE,         /* 文件存储区, 用于大块数据的存储, 操作方式与文件类似 */
    FZONE_CIRFILE,      /* 环形文件存储区, 用于大块数据的存储, 数据溢出后会覆盖最早的数据 */
    FZONE_CIRCULAR,     /* 环形存储区, 存储地址由模块内部分配, 单元数据大小可变, 用户按方向逐一读取 */
    FZONE_FREQUENCY,    /* 频繁存储区, 存储地址由模块内部分配, 数据长度固定 */
    FZONE_COUNT         /* 表征分区类型数目, 不是有效的分区类型 */
}fzone_type;

struct flash_data {
    int     id;         /* 数据ID */
    size_t  size;       /* 有效数据长度, 单位byte */
};

struct flash_zone {
    fzone_type          type;           /* 分区类型 */
    int                 zone_id;        /* 分区ID */
    const char          name[16];       /* 分区名称, 字符串 */
    union {
    struct {
    struct flash_data   *data;          /* 数据表 */
    size_t              count;          /* 数据个数 */
    }discrete, fatal;
    struct {
    size_t              total_size;     /* 分区总大小(表征用户数据量, 不等于实际分区大小), 单位byte */
    }direct, file, circular;
    struct {
    size_t              data_size;      /* 有效数据长度, 单位byte */
    size_t              total_size;     /* 分区总大小(表征用户数据量, 不等于实际分区大小), 单位byte */
    }frequency;
    };
};

#define FLASH_ZONE_NULL 0xEFFFFFFF      /* 空的分区ID */

int aad_flash_initialize(struct flash_adapter *);
void aad_flash_process(void);

int aad_flash_debug_set(char *loglevel);

int aad_flash_attribute_get(struct flash_attribute *);

int aad_flash_enter_lowpower(void);
int aad_flash_leave_lowpower(void);

aadl_cmd_state aad_flash_cmd_state_get(int id);
int aad_flash_callback_register(int id, void (*callback)(bool success, void *ptr), void *ptr);

int aad_flash_erase_chip(aadl_option option_set, unsigned int timeout);

int aad_flash_format(const struct flash_zone *, size_t count, aadl_option option_set, unsigned int timeout);
int aad_flash_format_fully(const struct flash_zone *, size_t count, aadl_option option_set, unsigned int timeout);

int aad_flash_direct_write(int zone_id, uint32_t offset_addr, const uint8_t *buf, size_t size, aadl_option option_set, unsigned int timeout);
ssize_t aad_flash_direct_read(int zone_id, uint32_t offset_addr, uint8_t *buf, size_t size);

int aad_flash_zone_write(int zone_id, int data_id, const uint8_t *buf, aadl_option option_set, unsigned int timeout);
ssize_t aad_flash_zone_read(int zone_id, int data_id, uint8_t *buf);

int aad_flash_frequency_write(int zone_id, const uint8_t *buf, aadl_option option_set, unsigned int timeout);
ssize_t aad_flash_frequency_read(int zone_id, uint8_t *buf);

#define FSEEK_START     0x00000000      /* 文件起始 */
#define FSEEK_END       0xFFFFFFFF      /* 文件末尾 */

int aad_flash_file_wseek(int zone_id, uint32_t offset);
int aad_flash_file_rseek(int zone_id, uint32_t offset);
int aad_flash_file_write(int zone_id, const uint8_t *buf, size_t size, aadl_option option_set, unsigned int timeout);
ssize_t aad_flash_file_read(int zone_id, uint8_t *buf, size_t size);
int aad_flash_file_erase(int zone_id, aadl_option option_set, unsigned int timeout);
int aad_flash_file_size_get(int zone_id, size_t *total_size, size_t *used_size);

#define FLASH_CIRCULAR_MOVE_ALL 0xFFFFFFFF  /* 转移整个环形存储区 */

int aad_flash_circular_write(int zone_id, const uint8_t *buf, size_t size, aadl_option option_set, unsigned int timeout);
ssize_t aad_flash_circular_read(int zone_id, uint8_t *buf, size_t max_size);
ssize_t aad_flash_circular_peek(int zone_id, uint8_t *buf, size_t max_size);
int aad_flash_circular_move(int dest_zone_id, int src_zone_id, unsigned int count, aadl_option option_set, unsigned int timeout);
ssize_t aad_flash_circular_count_get(int zone_id);
int aad_flash_circular_size_get(int zone_id, size_t *total_size, size_t *used_size);

int aad_flash_zone_clear(int zone_id, aadl_option option_set, unsigned int timeout);

#define AAD_FLASH_IDLE                  0x00
#define AAD_FLASH_ERASING_MASK          0x01
#define AAD_FLASH_FORMATING_MASK        0x02
#define AAD_FLASH_READING_MASK          0x04
#define AAD_FLASH_WRITING_MASK          0x08

//uint8_t aad_flash_status_get(void);

#ifdef __cplusplus
}
#endif

#endif /* __AADL_FLASH_H */
